# Copyright (c) 2018 Presto Labs Pte. Ltd.
# Author: inkyu

import logging
from collections import namedtuple

_LastFill = namedtuple('_LastFill',
                       [
                           'price',
                           'qty',
                           'qty_orig',
                           'qty_accum_fill',
                           'qty_accum_by_last_fill',
                           'from_last_fill',
                       ])


class LastFill(_LastFill):
  def to_str(self):
    if self.from_last_fill:
      fmt_str = '%f, (%f)%f/%f @ %f'
    else:
      fmt_str = '%f, %f(%f)/%f @ %f'
    return fmt_str % (
        self.qty, self.qty_accum_by_last_fill, self.qty_accum_fill, self.qty_orig, self.price)


class FillCollector(object):
  def __init__(self, *, price_orig, qty_orig, qty_threshold=1e-4, logger=None):
    self._logger = logger or logging.getLogger(__name__)

    # Fill by accum_fill
    self._price_avg_fill = 0.
    self._qty_accum_fill = 0.

    # Fill by last_fill
    self._qty_accum_by_last_fill = 0.

    self._price_orig = price_orig
    self._qty_orig = qty_orig
    self._qty_threshold = qty_threshold

  @property
  def fully_filled(self):
    return self._qty_handled() >= self._qty_orig - self._qty_threshold

  def _qty_handled(self):
    qty_handled = max(self._qty_accum_fill, self._qty_accum_by_last_fill)
    if qty_handled >= self._qty_orig + self._qty_threshold:
      self._logger.error('Too much qty: %f,%f/%f',
                         self._qty_accum_fill,
                         self._qty_accum_by_last_fill,
                         self._qty_orig)
    return qty_handled

  def update_by_remaining_qty(self, price_avg_fill, qty_remaining):
    return self.update_by_accum_fill(price_avg_fill, self._qty_orig - qty_remaining)

  def update_by_last_fill(self, price_last_fill, qty_last_fill):
    assert price_last_fill is not None
    assert qty_last_fill is not None
    qty_handled = self._qty_handled()
    self._qty_accum_by_last_fill += qty_last_fill
    qty_diff = self._qty_accum_by_last_fill - qty_handled
    if qty_diff > self._qty_threshold:
      return LastFill(from_last_fill=True,
                      qty_orig=self._qty_orig,
                      qty_accum_fill=self._qty_accum_fill,
                      qty_accum_by_last_fill=self._qty_accum_by_last_fill,
                      price=price_last_fill,
                      qty=qty_diff)
    else:
      return None

  def update_by_accum_fill(self, price_avg_fill, qty_accum_fill):
    qty_handled = self._qty_handled()
    qty_diff = qty_accum_fill - qty_handled
    pq_prev = self._price_avg_fill * self._qty_accum_fill
    qty_prev = self._qty_accum_fill
    if qty_accum_fill > self._qty_accum_fill:
      self._price_avg_fill = price_avg_fill
      self._qty_accum_fill = qty_accum_fill
    if qty_diff > self._qty_threshold:
      pq = price_avg_fill * qty_accum_fill
      price_last_fill = (pq - pq_prev) / (qty_accum_fill - qty_prev)
      return LastFill(from_last_fill=False,
                      qty_orig=self._qty_orig,
                      qty_accum_fill=self._qty_accum_fill,
                      qty_accum_by_last_fill=self._qty_accum_by_last_fill,
                      price=price_last_fill,
                      qty=qty_diff)
    else:
      return None
